--- author:liushiyin
--- date: 2019-10-09 13:43:52
--- des: #des#
local CommonScrollPage = class("CommonScrollPage", ccui.Layout)
local SchedulerHelper = require("app.utils.SchedulerHelper")

local PI = 3.1415926 -- acos(-1)
--//菜单的缩小比例 最小的比例是1-MENU_SCALE
local MENU_SCALE = 0.7

--//菜单的倾斜度 最多为45度
local MENU_ASLOPE = 40

--//calcFunction(x) 为 x/(x+a),其中a为常数
local CALC_A = 1

--//动画运行时间
local ANIM_TIME = 0.34

--//菜单项的大小与屏幕的比例，当然可以通过setContentSize设置
local CONTENT_SIZE_SCALE = (7 / 10)

--//菜单项长度与菜单长度的比例 滑动一个菜单项长度，菜单项变化一个
local ITEM_SIZE_SCALE = (2.6 / 10)

local CACHE_SIZE = 5

function CommonScrollPage:ctor(size)
    self._size = size or cc.size(1136, 500)
    if self.onCreate then
        self:onCreate()
    end
end

function CommonScrollPage:setCallback(callback)
    self._callback = callback
end

function CommonScrollPage:onCreate()
    self._index = 1
    self._lastIndex = 1
    self._items = {}
    -- self:ignoreAnchorPointForPosition(false)
    self._selectedItem = nil
    self:setAnchorPoint(cc.p(0.5, 0.5))
    local size = cc.Director:getInstance():getWinSize()
    self:setContentSize(self._size)

    local listener = cc.EventListenerTouchOneByOne:create()
    listener:setSwallowTouches(false)
    listener:registerScriptHandler(handler(self, self.onTouchBegan), cc.Handler.EVENT_TOUCH_BEGAN)
    listener:registerScriptHandler(handler(self, self.onTouchMoved), cc.Handler.EVENT_TOUCH_MOVED)
    listener:registerScriptHandler(handler(self, self.onTouchEnded), cc.Handler.EVENT_TOUCH_ENDED)
    cc.Director:getInstance():getEventDispatcher():addEventListenerWithSceneGraphPriority(listener, self)
end

function CommonScrollPage:clear()
    self:removeAllChildren()
    self._items = {}
    self._selectedItem = nil
    self:reset()
end

function CommonScrollPage:setParams(params)
    self._params = params
    self._dataList = params.dataList
    self._count = #self._dataList
    self._template = params.template
    self._offset = params.offset or 0
    self._orbitOffset = params.orbitOffset or 1
    self._scaleOffset = params.scaleOffset or 1
    self:clear()
end

function CommonScrollPage:_pushItem(item, index)
    self._items[index] = item
end

function CommonScrollPage:_addMenuItem(index)
    local item = self._template.new()
    item:updateUI(self._dataList[index])
    item:setPosition(cc.p(-100, self:getContentSize().height * 0.5))
    self:addChild(item)
    self:_pushItem(item, index)
    return item
end

function CommonScrollPage:setSelectedIndex(index)
    self._index = index
    -- logWarn("CommonScrollPage:setSelectedIndex " .. index)
    self:updatePosition(self:getForeachIndex())
end

function CommonScrollPage:getForeachIndex()
    local startIndex = math.ceil(self._index - CACHE_SIZE)
    startIndex = startIndex < 1 and 1 or startIndex
    local endIndex = math.ceil(self._index + CACHE_SIZE)
    endIndex = endIndex > self._count and self._count or endIndex
    -- logWarn("startIndex " .. startIndex .. " endINdex" .. endIndex)
    return startIndex, endIndex
end

function CommonScrollPage:_calcOrbitFrom(i)
    local orbitRatio = self:calcFunction(i - self._lastIndex, MENU_ASLOPE)
    if i < self._index then
        orbitRatio = orbitRatio * self._orbitOffset
    end
    return orbitRatio
end

function CommonScrollPage:_calcOrbitRatio(i)
    local orbitRatio =
        self:calcFunction(i - self._index, MENU_ASLOPE) - self:calcFunction(i - self._lastIndex, MENU_ASLOPE)
    if i < self._index then
        orbitRatio = orbitRatio
    end
    return orbitRatio
end

function CommonScrollPage:_calcScaleY(i)
    local scaleRatio = (1.0 - math.abs(self:calcFunction(math.abs(i - self._index), MENU_SCALE)))
    if i < self._index then
        scaleRatio = scaleRatio * self._scaleOffset
    end
    return scaleRatio
end

function CommonScrollPage:_calcScaleX(i)
    local scaleRatio = (1.0 - math.abs(self:calcFunction(math.abs(i - self._index), MENU_SCALE)))
    if i < self._index then
        scaleRatio = scaleRatio * (scaleRatio + 0.05)
    end
    return scaleRatio
end

function CommonScrollPage:_calcX(i, menuSize)
    local x = self:calcFunction(i - self._index, menuSize.width / 2)
    if i <= self._index then
        x = x + self._offset
    end
    return x
end

function CommonScrollPage:updatePosition(startIndex, endIndex)
    local menuSize = self:getContentSize()
    for i = startIndex, endIndex do
        local item = self._items[i]
        if not item then
            item = self:_addMenuItem(i)
        end
        -- //设置位置
        local orbitRatio = self:_calcOrbitRatio(i)
        local x = self:_calcX(i, menuSize)
        item:setPosition(cc.p(menuSize.width / 2 + x, menuSize.height / 2))
        --//设置zOrder,即绘制顺序
        item:setLocalZOrder(-math.abs((i - self._index) * 100))
        --//设置伸缩比例
        item:setScaleX(self:_calcScaleX(i))
        item:setScaleY(self:_calcScaleY(i))
        if item.changeRatio then
            item:changeRatio(math.abs(i - self._index))
        end
        --//设置倾斜，Node没有setCamera函数，将OrbitCamera的运行时间设为0来达到效果
        local orbit1 = cc.OrbitCamera:create(0, 1, 0, self:_calcOrbitFrom(i), orbitRatio, 0, 0)
        item:runAction(orbit1)
    end
end

function CommonScrollPage:updatePositionWithAnimation(startIndex, endIndex)
    --//先停止所有可能存在的动作
    self:_stopAllActions()
    local menuSize = self:getContentSize()
    for i = startIndex, endIndex do
        local item = self._items[i]
        if not item then
            item = self:_addMenuItem(i)
        end
        item:setLocalZOrder(-math.abs((i - self._index) * 100))
        local x = self:_calcX(i, menuSize)
        local moveTo = cc.MoveTo:create(ANIM_TIME, cc.p(menuSize.width / 2 + x, menuSize.height / 2))
        local moveTo = cc.EaseSineInOut:create(moveTo)
        local scaleTo = cc.ScaleTo:create(ANIM_TIME, self:_calcScaleX(i), self:_calcScaleY(i))
        local orbitRatio = self:_calcOrbitRatio(i)
        local orbit1 = cc.OrbitCamera:create(ANIM_TIME, 1, 0, self:_calcOrbitFrom(i), orbitRatio, 0, 0)
        if item.changeRatio then
            item:changeRatio(math.abs(i - self._index), ANIM_TIME)
        end
        local spawn = cc.Spawn:create(moveTo, scaleTo, orbit1)
        item:runAction(spawn)
    end
end

function CommonScrollPage:reset()
    self._lastIndex = 1
    self._index = 1
end

function CommonScrollPage:setIndex(index)
    self._lastIndex = self._index
    self._index = index
end

function CommonScrollPage:getIndex()
    local index = self:_fourSubFiveAdd(self._index)
    if (index < 1) then
        index = 1
    end
    if (index > self._count) then
        index = self._count
    end
    return index
end

function CommonScrollPage:getCurrentItem()
    if (self._count == 0) then
        return
    end
    local index = self:getIndex()
    if not self._items[index] then
        self:_addMenuItem(index)
    end
    return self._items[index]
end

function CommonScrollPage:_stopAllActions()
    for _, item in pairs(self._items) do
        item:stopAllActions()
    end
end

function CommonScrollPage:onTouchBegan(touch, event)
    --//先停止所有可能存在的动作
    self:_stopAllActions()
    if (self._selectedItem) then
        self._selectedItem:unselected()
    end
    self._startIndex = self:getIndex()
    local position = self:convertToNodeSpace(touch:getLocation())
    local size = self:getContentSize()
    local rect = {x = 0, y = 0, width = size.width, height = size.height}
    if (cc.rectContainsPoint(rect, position)) then
        return true
    end
    return false
end

function CommonScrollPage:onTouchEnded(touch, event)
    local size = self:getContentSize()
    local xDelta = touch:getLocation().x - touch:getStartLocation().x
    self:rectify(xDelta)
    if (math.abs(xDelta) < size.width / 24 and self._selectedItem) then
        self._selectedItem:activate()
    end
    self:updatePositionWithAnimation(self:getForeachIndex())
    SchedulerHelper.newScheduleOnce(handler(self, self.actionEndCallBack), ANIM_TIME)
    return
end

function CommonScrollPage:onTouchMoved(touch, event)
    local xDelta = touch:getDelta().x
    local size = self:getContentSize()
    self._lastIndex = self._index
    self._index = self._index - xDelta / (size.width * ITEM_SIZE_SCALE)
    self:updatePosition(self:getForeachIndex())
    return
end

function CommonScrollPage:rectify(forward)
    local index = self._index
    index = self:_fourSubFiveAdd(index)
    if (forward) > 10 then
        index = index - 1
    elseif (forward) < -10 then
        index = index
    else
        index = self._startIndex
    end
    if (index < 1) then
        index = 1
    end
    if (index > self._count) then
        index = self._count
    end
    self:setIndex(index)
end

function CommonScrollPage:_fourSubFiveAdd(index)
    local offset = index - math.ceil(index)
    if offset > 0.5 then
        return math.ceil(index) + 1
    else
        return math.ceil(index)
    end
end

function CommonScrollPage:actionEndCallBack(dx)
    self._selectedItem = self:getCurrentItem()
    if (self._selectedItem) then
        self._selectedItem:selected(self:getIndex())
    end
    self:_clearNotInScreen()
    if self._callback then
        self._callback(self:getIndex())
    end
end

function CommonScrollPage:_clearNotInScreen()
    local startIndex, endINdex = self:getForeachIndex()
    for i = 1, startIndex - 1 do
        if self._items[i] then
            self._items[i]:removeFromParent()
            self._items[i] = nil
        end
    end
    for i = endINdex + 1, self._count do
        if self._items[i] then
            self._items[i]:removeFromParent()
            self._items[i] = nil
        end
    end
end

function CommonScrollPage:calcFunction(index, width)
    return width * index / (math.abs(index) + CALC_A)
end

return CommonScrollPage
